/*------------------------------------------------------------------------
 *
 * 前端代码的查询取消支持
 *
 * 各种实用函数用于通过SIGINT信号处理程序控制查询取消。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/fe-utils/cancel.c
 *
 *------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <unistd.h>

#include "common/connect.h"
#include "fe_utils/cancel.h"
#include "fe_utils/string_utils.h"


/*
 * 将简单的字符串写入stderr --- 必须在信号处理程序中安全。
 * 我们忽略write()的结果，因为对此我们无能为力。
 * 某些编译器使得这比应该的更难。
 */
#define write_stderr(str) \
	do { \
		const char *fc_str_ = (str); \
		int		fc_rc_; \
		fc_rc_ = write(fileno(stderr), fc_str_, strlen(fc_str_)); \
		(void) fc_rc_; \
	} while (0)

/*
 * 包含从数据库连接到后端取消查询所需的所有信息。
 */
static PGcancel *volatile cancelConn = NULL;

/*
 * 预先确定的本地化错误字符串 --- 需要避免尝试
 * 从信号处理程序中调用gettext()。
 */
static const char *cancel_sent_msg = NULL;
static const char *cancel_not_sent_msg = NULL;

/*
 * 当我们接收到SIGINT（或本地等效信号）时，设置CancelRequested。
 * 本模块没有重置它的条款；但是应用程序
 * 可以选择在成功从取消中恢复后清除它。
 * 请注意，我们没有保证成功发送了取消请求，
 * 或者如果我们确实发送了请求，那么该请求将会产生任何效果。
 */
volatile sig_atomic_t CancelRequested = false;

#ifdef WIN32
static CRITICAL_SECTION cancelConnLock;
#endif

/*
 * 额外的取消回调。
 */
static void (*cancel_callback) (void) = NULL;


/*
 * SetCancelConn
 *
 * 设置cancelConn指向当前的数据库连接。
 */
void SetCancelConn(PGconn *fc_conn)
{
	PGcancel   *fc_oldCancelConn;

#ifdef WIN32
	EnterCriticalSection(&cancelConnLock);
#endif

	/* 如果我们有旧的连接则释放它 */
	fc_oldCancelConn = cancelConn;

	/* 确保handle_sigint在释放时不使用指针 */
	cancelConn = NULL;

	if (fc_oldCancelConn != NULL)
		PQfreeCancel(fc_oldCancelConn);

	cancelConn = PQgetCancel(fc_conn);

#ifdef WIN32
	LeaveCriticalSection(&cancelConnLock);
#endif
}

/*
 * ResetCancelConn
 *
 * 释放当前的取消连接（如果有），并设置为NULL。
 */
void ResetCancelConn(void)
{
	PGcancel   *fc_oldCancelConn;

#ifdef WIN32
	EnterCriticalSection(&cancelConnLock);
#endif

	fc_oldCancelConn = cancelConn;

	/* 确保handle_sigint在释放时不使用指针 */
	cancelConn = NULL;

	if (fc_oldCancelConn != NULL)
		PQfreeCancel(fc_oldCancelConn);

#ifdef WIN32
	LeaveCriticalSection(&cancelConnLock);
#endif
}


/*
 * 支持查询取消的代码
 *
 * 请注意，从信号处理程序直接发送取消是安全的
 * 因为PQcancel()是为了确保这一点而编写的。我们使用write()来报告
 * 到stderr，因为在信号处理程序中使用简单的设施更好。
 *
 * 在Windows上，信号取消发生在一个单独的线程中，因为
 * 这就是SetConsoleCtrlHandler的工作方式。PQcancel函数是安全的
 * （与PQrequestCancel不同）。但是，需要一个CRITICAL_SECTION
 * 来保护PGcancel结构，以防在信号线程使用时被更改。
 */

#ifndef WIN32

/*
 * handle_sigint
 *
 * 处理中断信号，通过取消当前命令（如果cancelConn
 * 被设置）。
 */
static void fc_handle_sigint(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;
	char		fc_errbuf[256];

	CancelRequested = true;

	if (cancel_callback != NULL)
		cancel_callback();

	/* 如果我们正在处理数据库查询，则发送QueryCancel */
	if (cancelConn != NULL)
	{
		if (PQcancel(cancelConn, fc_errbuf, sizeof(fc_errbuf)))
		{
			write_stderr(cancel_sent_msg);
		}
		else
		{
			write_stderr(cancel_not_sent_msg);
			write_stderr(fc_errbuf);
		}
	}

	errno = fc_save_errno;			/* 以防写入操作更改了它 */
}

/*
 * setup_cancel_handler
 *
 * 为SIGINT注册查询取消回调。
 */
void setup_cancel_handler(void (*fc_callback) (void))
{
	cancel_callback = fc_callback;
	cancel_sent_msg = _("Cancel request sent\n");
	cancel_not_sent_msg = _("Could not send cancel request: ");

	pqsignal(SIGINT, fc_handle_sigint);
}

#else							/* WIN32 */

static BOOL WINAPI consoleHandler(DWORD dwCtrlType)
{
	char		errbuf[256];

	if (dwCtrlType == CTRL_C_EVENT ||
		dwCtrlType == CTRL_BREAK_EVENT)
	{
		CancelRequested = true;

		if (cancel_callback != NULL)
			cancel_callback();

		/* 如果我们正在处理数据库查询，则发送QueryCancel */
		EnterCriticalSection(&cancelConnLock);
		if (cancelConn != NULL)
		{
			if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
			{
				write_stderr(cancel_sent_msg);
			}
			else
			{
				write_stderr(cancel_not_sent_msg);
				write_stderr(errbuf);
			}
		}

		LeaveCriticalSection(&cancelConnLock);

		return TRUE;
	}
	else
		/* 对于未处理的信号返回FALSE */
		return FALSE;
}

void setup_cancel_handler(void (*callback) (void))
{
	cancel_callback = callback;
	cancel_sent_msg = _("Cancel request sent\n");
	cancel_not_sent_msg = _("Could not send cancel request: ");

	InitializeCriticalSection(&cancelConnLock);

	SetConsoleCtrlHandler(consoleHandler, TRUE);
}

#endif							/* WIN32 */
